/**
 * Copyright © 2014 Creativor Studio.
 * About license information, please see LICENSE.txt.
 */
package org.rayson.server.service;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.rayson.api.KeyValues;
import org.rayson.api.Protocol;
import org.rayson.api.exception.RpcException;
import org.rayson.api.server.FilterChain;
import org.rayson.api.server.ServerRequest;
import org.rayson.api.server.ServerResponse;
import org.rayson.api.server.ServiceFilter;
import org.rayson.api.server.exception.ServiceException;
import org.rayson.api.struct.HttpResponseStatus;
import org.rayson.share.RaysonConstants;
import org.rayson.share.logger.RasonLoggerFactory;
import org.slf4j.Logger;

/**
 * An default {@link ServiceFilter}. It will do invoke the RPC request of giving
 * {@link Protocol} service implementation.
 * 
 * @author creativor
 */
class DefaultServiceFilter implements ServiceFilter {

	private class MethodMirror {
		private final Method method;
		private final String[] methodMeta;
		final Class<?>[] paramTypes;

		MethodMirror(final Method method) throws ServiceException {
			this.method = method;
			final String methodName = method.getName();
			methodMeta = methodsMeta.get(methodName);
			if (methodMeta == null)
				throw new ServiceException(method + " found no meta info");
			paramTypes = method.getParameterTypes();
			if (paramTypes.length != methodMeta.length)
				throw new ServiceException(method + " parameters length is "
						+ paramTypes.length + ", but the meta info shows "
						+ methodMeta.length);
		}

		public void invoke(final ServerRequest request,
				final ServerResponse response) throws ServiceException {

			final Object[] args = parseArguments(request.getInvocation()
					.getParameters());

			final Object result;
			try {
				result = method.invoke(service, args);
			} catch (final IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (final IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (final InvocationTargetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		/**
		 * @param paraJson
		 * @param class1
		 * @return
		 */
		private Object parseArgument(final String paraJson,
				final Class<?> class1) {
			// TODO Auto-generated method stub
			return null;
		}

		private Object[] parseArguments(final KeyValues parameters) {
			final Object[] params = new Object[paramTypes.length];
			String paramName;
			String paraJson;
			for (int i = 0; i < paramTypes.length; i++) {
				paramName = methodMeta[i];
				paraJson = parameters.get(paramName);
				params[i] = parseArgument(paraJson, paramTypes[i]);
			}
			return params;
		}
	}

	private static final Logger LOGGER = RasonLoggerFactory
			.getLogger(DefaultServiceFilter.class);
	private Properties properties;
	private Protocol service;
	final HashMap<String, String[]> methodsMeta;

	/**
	 * Map of <Method URL, Method Mirror>.
	 */
	private final Map<String, MethodMirror> methodCache;

	DefaultServiceFilter(final Class<? extends Protocol> protocolInerface)
			throws ServiceException {

		methodCache = new HashMap<String, MethodMirror>();

		// Load meta classes.
		final String metaClsName = protocolInerface.getName()
				+ RaysonConstants.SERVICE_META_CLASS_SUFFIX;
		final Class metaCls;
		try {
			metaCls = Class.forName(metaClsName);
		} catch (final ClassNotFoundException e) {
			throw new ServiceException("Meta data class " + metaClsName
					+ " not found");
		}
		final Field methodField;
		try {
			methodField = metaCls
					.getField(RaysonConstants.SERVICE_META_CLASS_METHODS_FIELD);
		} catch (NoSuchFieldException | SecurityException e) {
			throw new ServiceException("No field "
					+ RaysonConstants.SERVICE_META_CLASS_METHODS_FIELD
					+ " found in " + metaCls);
		}

		try {
			methodsMeta = (HashMap<String, String[]>) methodField.get(null);
		} catch (final Exception e) {
			throw new ServiceException("Can not get filed  "
					+ RaysonConstants.SERVICE_META_CLASS_METHODS_FIELD
					+ " value of " + metaCls);
		}
		// Initialize methods cache.
		final Method[] methods = protocolInerface.getDeclaredMethods();
		for (final Method method : methods) {
			addMethod(method);
		}
	}

	/**
	 * @param method
	 * @throws ServiceException
	 */
	private void addMethod(final Method method) throws ServiceException {
		final String name = method.getName();
		// Check cache
		if (methodCache.containsKey(name))
			throw new ServiceException("Duplicate method with same name "
					+ name);
		methodCache.put(name, new MethodMirror(method));
	}

	@Override
	public void init(final Protocol service, final Properties properties)
			throws ServiceException {
		this.service = service;
		this.properties = properties;

	}

	@Override
	public void invoke(final ServerRequest request,
			final ServerResponse response, final FilterChain chain)
			throws ServiceException {
		// Just invoke the request in service.
		LOGGER.debug(request + " invoking in " + DefaultServiceFilter.class);
		// Do invoking the request.
		final String methodName = request.getInvocation().getMethod();
		final MethodMirror methodMirror = methodCache.get(methodName);
		if (methodMirror == null) {
			response.setException(new RpcException(HttpResponseStatus.NOT_FOUND));
			return;
		}

		methodMirror.invoke(request, response);

		if (chain != null)
			chain.invoke(request, response);

	}
}